package main;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;

import parser.*;
import datatype.*;

/**
 * @author Artur Tolstenco
 * 
 */
public class CasdProject {
	private static final boolean DEBUG = true;
	private static final String FILE_EXTENSION = "txt";

	/**
	 * The method that takes into input the representation of input file and 
	 * initializes the Graph
	 * @param readedFromFile in memory representation of the input file
	 * @return the corresponding graph
	 * @throws AlreadyExistigEdgeException if the edge (arc) is already 
	 * 			present in the list of adjacencies
	 * @throws NegativeWeightEdgeException if the edge (arc) have a negative weight
	 * @throws AlreadyExistingVertexException if there are at least two 
	 * 			vertices with the same name
	 * @throws SameSourceAndDestinationEdge if the source and destination of the edge
	 * 										are the same
	 */
	private static Graph initializeGraph(ArrayList<HashMap<String, ArrayList<HashMap<String, Double>>>> readedFromFile)
			throws AlreadyExistigEdgeException, NegativeWeightEdgeException, AlreadyExistingVertexException, SameSourceAndDestinationEdge {
		Graph graph = new Graph();
		ArrayList<String> verticesString = new ArrayList<String>();
		
		for (HashMap<String, ArrayList<HashMap<String, Double>>> row : readedFromFile) {
			String sourceVertexName = row.keySet().toArray(new String[0])[0];
			
			for (HashMap<String, Double> arc : row.get(sourceVertexName)) {
				String targetVertexName = arc.keySet().toArray(new String[0])[0];
				
				Double arcWeight = arc.get(targetVertexName);

				graph.addArc(sourceVertexName, targetVertexName, arcWeight);
			}
			
			if (verticesString.contains(sourceVertexName))
				throw new AlreadyExistingVertexException();
			else
				verticesString.add(sourceVertexName);
		}

		return graph;
	}

	/**
	 * Given a target vertex this method returns a list of vertices that is the
	 * shortest path to target vertex
	 * @param vertexTo the vertex to which calculate the shortest path
	 * @param dijkstraVertices array of dijkstraVertices that are part of the general path
	 * @return a list of vertices
	 */
	private static List<DijkstraVertex> getShortestPathToVertex(DijkstraVertex vertexTo, DijkstraVertex dijkstraVertices[]) {
		List<DijkstraVertex> path = new ArrayList<DijkstraVertex>();
		DijkstraVertex temp;

		for (temp = vertexTo; temp.getParent() != null; temp = dijkstraVertices[temp.getParent().getIndex()])
			path.add(temp);
		
		path.add(temp);
		
		Collections.reverse(path);

		return path;
	}

	/**
	 * Given the graph this method writes the all paths to all targets in
	 * the graph (shortest paths)
	 * @param dijkstraVertices the vertices returned by DijkstraAlgorithm (that 
	 * represent the shortest path)
	 * @param inputfilename the part of input file name is used to give name to
	 * the output file name (with the results)
	 */
	private static void writeResults(DijkstraVertex dijkstraVertices[], String inputfilename) {
		FileWriter fileWriter = null;
		BufferedWriter outFile = null;
		List<List<DijkstraVertex>> paths = new ArrayList<List<DijkstraVertex>>();

		String outputfilename = inputfilename.substring(0, inputfilename.lastIndexOf("."))
								+ "_output." + FILE_EXTENSION;

		try {
			fileWriter = new FileWriter(outputfilename);
		} catch (IOException e) {
			System.out.println("Error creating a file! Please check the rights of the program!");
			e.printStackTrace();
			System.exit(6);
		}

		outFile = new BufferedWriter(fileWriter);	
		
		if (DEBUG) 
			System.out.println("The output is:\n");
		
		for (DijkstraVertex vertex : dijkstraVertices) 
			if (vertex.getMinimumDistance() != 0 && vertex.getMinimumDistance() != Double.POSITIVE_INFINITY) 
				paths.add(getShortestPathToVertex(vertex, dijkstraVertices));

		//sort by target vertex
		Collections.sort(paths, new Comparator<List<DijkstraVertex>>() {
			public int compare(List<DijkstraVertex> o1, List<DijkstraVertex> o2) {
				return o1.get(o1.size()-1).getVertex().toString().compareTo(o2.get(o2.size()-1).getVertex().toString());
			}
		});
		
		for (List<DijkstraVertex> path : paths) {
			String row = "";
			for (DijkstraVertex v : path) {
				row += v.getVertex() + " -> ";
			}
			
			row = row.substring(0, row.length()-4) + " : " + path.get(path.size()-1).getMinimumDistance() + "\n";

			if (DEBUG)
				System.out.print(row);
			
			try {
				outFile.write(row);
			} catch (IOException e) {
				System.out.println("Error while writing the file!");
				e.printStackTrace();
				
				try {
					outFile.close();
				} catch (IOException e1) {
					System.out.println("Error while closing the file before tenting to write to the file!");
					e.printStackTrace();
					System.exit(6);
				}
				
				System.exit(6);
			}
		}
		
		if (DEBUG)
			System.out.println("\n===========================================================\n");
		
		try {
			outFile.close();
		} catch (IOException e) {
			System.out.println("Error while closing the file!");
			e.printStackTrace();
			System.exit(6);
		}
		
	}

	public static void main(String[] args) {
		File file;
		Parser parser;
		boolean parserOk = false;

		Graph graph = null;

		if (args.length != 1) {
			System.out.println("Sorry, you should provide an input file as parameter! (only one!)");
			System.exit(1);
		}

		file = new File(args[0]);

		if (!file.exists() || !file.isFile() || 
				!file.getName().substring(file.getName().lastIndexOf(".") + 1).equals(FILE_EXTENSION)) {
			
			System.out.println("Sorry, the filename you provided does not exists or it is not a valid file!");
			System.out.println("The file must be a text file with '" + FILE_EXTENSION + "' extension");
			System.exit(2);
		}

		parser = new Parser(file);

		try {
			parserOk = parser.process();
		} catch (FileNotFoundException e) {
			System.out.println("Error: file not found when trying to parse the file!");
			e.printStackTrace();
		}

		if (!parserOk) {
			System.out.println("Error parsing the file. Please check the format!");
			System.exit(3);
		}

		try {
			graph = initializeGraph(parser.getResult());
		} catch (AlreadyExistigEdgeException e) {
			System.out.println("There was an error during the initialization of graph!\nTry again please!");
			System.exit(4);
		} catch (NegativeWeightEdgeException e) {
			System.out.println("There was an error during the initialization of graph!\nTry again please!");
			System.exit(4);
		} catch (AlreadyExistingVertexException e) {
			System.out.println("There was an error during the initialization of graph!\nTry again please!");
			System.exit(4);
		} catch (SameSourceAndDestinationEdge e) {
			System.out.println("There was an error during the initialization of graph!\nTry again please!");
			System.exit(4);
		}

		if (DEBUG) {
			System.out.println("The content just readed from the file is:\n");
			System.out.println(graph);
			System.out.println("===========================================================\n");
		}

		
		DijkstraVertex[] treeAndDistance = DijkstraAlgorithm.searchShortestPath(graph, graph.nodes().get(0));		
		
		writeResults(treeAndDistance, args[0]);
		
		System.out.println("The program had finished with success!\nPlease " +
						   "check the output file: " + 
						   args[0].substring(0, args[0].lastIndexOf("."))	+ 
						   "_output." + FILE_EXTENSION);
		
		System.exit(0);
	}

}
